/*
 *  Copyright (C) 2011 Jaime Pavlich-Mariscal
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package cl.ucn.disc.biblio.refcluster;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.StringUtils;

import cl.ucn.disc.biblio.refcluster.MultiCounter.ItemCounter;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ReferenceManager {

    public static final Logger LOGGER = Logger.getLogger(ReferenceClusterer.class.getName());
    private FileFilter wosFilesFilter = new FileFilter() {

        @Override
        public boolean accept(File pathname) {
            String filename = pathname.getName().toLowerCase();
            return filename.endsWith(".txt");
        }
    };

    /**
     * Creates a list of references from a file in WoS format
     *
     * @param wosFile
     * @return
     * @throws FileNotFoundException
     */
    public Set<Reference> readRefsFromWoSFile(File wosFile) throws FileNotFoundException {
        Scanner s = new Scanner(wosFile);

        Set<Reference> refs = new TreeSet<Reference>();
        boolean addingRefs = false;

        while (s.hasNextLine()) {
            String line = s.nextLine();
            if (line.trim().endsWith("DOI")) { // Concatenates the DOI if it is
                // in the next line
                line = line.concat(s.nextLine());
            }

            try {
                if (line.startsWith("CR ")) {
                    addingRefs = true;
                    refs.add(new Reference(line.substring(3)));
                } else if (addingRefs && line.startsWith(" ")) {
                    refs.add(new Reference(line.substring(3)));
                } else {
                    addingRefs = false;
                }
            } catch (InvalidReferenceException e) {
                // do nothing
            }

        }

        return refs;

    }

    public List<Reference> readRefsFromListFile(File refsFile) throws FileNotFoundException {
        Scanner s = new Scanner(refsFile);
        List<Reference> refs = new ArrayList<Reference>();
        while (s.hasNextLine()) {
            String line = s.nextLine();

            try {
                refs.add(new Reference(line));
            } catch (RuntimeException e) {
                System.out.println(e);
            } catch (InvalidReferenceException e) {
                // do nothing
            }
        }
        return refs;

    }

    public Set<Reference> readRefsFromWoSFolder(File wosDBFolder) throws FileNotFoundException {

        // Get all references from DB folder. Files in that folder must have WoS
        // format
        // and must be of the form "*.txt"
        Set<Reference> refs = new TreeSet<Reference>();
        for (File f : wosDBFolder.listFiles(wosFilesFilter)) {
            refs.addAll(readRefsFromWoSFile(f));
            LOGGER.log(Level.INFO,"Adding file: {0}", f.getName());
        }
        return refs;
    }

    public void saveRefsToList(Collection<Reference> refs, PrintWriter w) throws FileNotFoundException {
        for (Reference r : refs) {
            w.println(r.originalString);
        }
        w.flush();
    }

    /** Parses a set of references in WoS file of a source folder and writes a new one in the
     * specified destination folder with the references replaced by a set of replacements
     * obtained from a cluster file
     * @see #replaceRefs(File, File, Map)
     * @param srcFile
     * @param destFolder
     * @param clusterFile
     * @throws FileNotFoundException
     */
    public void replaceRefsOfFolder(File srcFolder, File destFolder, File clusterFile) throws FileNotFoundException {
        ReferenceClusterer rc = new ReferenceClusterer();
        Collection<Set<Reference>> clusters = rc.readClusters(clusterFile);
        Map<Reference, String> replacements = rc.createReplacementsFromClusters(clusters);
        replaceRefsOfFolder(srcFolder, destFolder, replacements);
    }

    /**
     * Parses a set of references in a WoS file and writes a new one in the
     * specified folder with the references replaced by a set of replacements
     *
     * @param srcFile
     * @param destFolder
     * @param replacements
     * @throws FileNotFoundException
     */
    private void replaceRefs(File srcFile, File destFolder, Map<Reference, String> replacements) throws FileNotFoundException {
        Scanner s = new Scanner(srcFile);
        destFolder.mkdirs();
        File destFile = new File(destFolder, srcFile.getName());
        PrintWriter w = new PrintWriter(destFile);

        boolean readingRefs = false;

        while (s.hasNextLine()) {

            // Reads the line from the source file
            String line = s.nextLine();

            // Concatenates the DOI if it is in the next line
            if (line.trim().endsWith("DOI")) {
                line = line + " " + s.nextLine().substring(3);
            }

            // Verifies if the line corresponds to a bibliographic reference
            // This happens either if the line starts with "CR" or if it is
            // among the following lines of a line that starts with CR
            try {
                if (line.startsWith("CR ")) {
                    readingRefs = true;
//				line = "CR " + replace(line.substring(3), replacements);
                    line = "CR " + replace(new Reference(line.substring(3)), replacements);
                } else if (readingRefs && line.startsWith(" ")) {
                    line = line.substring(0, 3) + replace(new Reference(line.substring(3)), replacements);
                } else { // In this case the line is not a biblio reference
                    readingRefs = false;
                }
            } catch (InvalidReferenceException e) {
                System.out.println(line + "\n\t" + e);
            }

            // write the destination file
            w.println(line);
        }

    }

    private String replace(Reference r, Map<Reference, String> replacements) {
        String rep = replacements.get(r);
        if (rep == null) {
            return r.ref;
        } else {
            return rep;
        }
    }

    /** Replaces the references of all WoS files in a folder.
     * @see ReferenceManager#replaceRefs(File, File, Map)
     * @param srcFolder
     * @param destFolder
     * @param replacements
     * @throws FileNotFoundException
     */
    private void replaceRefsOfFolder(File srcFolder, File destFolder, Map<Reference, String> replacements) throws FileNotFoundException {
        for (File f : srcFolder.listFiles(wosFilesFilter)) {
            replaceRefs(f, destFolder, replacements);
        }
    }

    /** Creates a "super reference", i.e., a reference having the longest sentences
     * for each field found in a list of references
     * @param refs
     * @return
     */
    public Reference createSuperReference(Set<Reference> refs) {




        Comparator<MultiCounter.ItemCounter<String>> c = new Comparator<MultiCounter.ItemCounter<String>>() {

            @Override
            public int compare(MultiCounter.ItemCounter<String> o1, MultiCounter.ItemCounter<String> o2) {
                int cmp = -o1.count.compareTo(o2.count);

                if (cmp == 0) {
                    cmp = -o1.element.compareTo(o2.element);
                }
                return cmp;
            }
        };

        MultiCounter<String> authorCount = new MultiCounter<String>(c, false);
        MultiCounter<String> yearCount = new MultiCounter<String>(c, false);
        MultiCounter<String> journalCount = new MultiCounter<String>(c, false);
        MultiCounter<String> volCount = new MultiCounter<String>(c, false);
        MultiCounter<String> pageCount = new MultiCounter<String>(c, false);
        MultiCounter<String> doiCount = new MultiCounter<String>(c, false);



        for (Reference ref : refs) {
            String author = StringUtils.trimToNull(ref.getAuthor());
            String year = StringUtils.trimToNull(ref.getYear());
            String journal = StringUtils.trimToNull(ref.getJournal());
            String volume = StringUtils.trimToNull(ref.getVolume());
            String page = StringUtils.trimToNull(ref.getPage());
            String doi = StringUtils.trimToNull(ref.getDoi());

            authorCount.add(author);
            yearCount.add(year);
            journalCount.add(journal);
            volCount.add(volume);
            pageCount.add(page);
            doiCount.add(doi);


        }

        String author = authorCount.isEmpty() ? null : StringUtils.trimToNull(authorCount.orderedCountersIterator().next().element);
        String year = yearCount.isEmpty() ? null : StringUtils.trimToNull(yearCount.orderedCountersIterator().next().element);
        String journal = journalCount.isEmpty() ? null : StringUtils.trimToNull(journalCount.orderedCountersIterator().next().element);
        String volume = volCount.isEmpty() ? null : StringUtils.trimToNull(volCount.orderedCountersIterator().next().element);
        String page = pageCount.isEmpty() ? null : StringUtils.trimToNull(pageCount.orderedCountersIterator().next().element);
        String doi = doiCount.isEmpty() ? null : StringUtils.trimToNull(doiCount.orderedCountersIterator().next().element);



        return new Reference(author, year, journal, volume, page, doi);


    }
}
